package com.mvc.rpc;

import com.mvc.rpc.annotation.EnableRpcProxies;
import com.mvc.rpc.annotation.EnableRpcProxy;
import com.mvc.rpc.annotation.MetaFeignClient;
import com.mvc.rpc.feign.RemoteProcedureCallFeign;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactoryBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * Rpc Bean扫描
 * @author JayEinstein
 */
public class RpcProxyScannerRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        // 动态地址开关
        boolean dynamicAddr = true;
        // 覆盖代理接口子类开关
        boolean coverProxy = true;
        AnnotationAttributes client = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MetaFeignClient.class.getName()));
        if (client != null) {
            dynamicAddr = client.getBoolean("dynamicAddr");
            coverProxy = client.getBoolean("coverProxy");
        }

        // 和nacos配合使用，如果nacos不开启，则host必填
        boolean nacosEnable = true;
        String enabled = environment.getProperty("spring.cloud.nacos.discovery.enabled");
        if (Boolean.FALSE.toString().equals(enabled)) {
            nacosEnable = false;
        }

        Set<AnnotationAttributes> result = new LinkedHashSet<>();

        // Direct annotation present?
        addAttributesIfNotNull(result, importingClassMetadata.getAnnotationAttributes(EnableRpcProxy.class.getName()));

        // Container annotation present?
        Map<String, Object> container = importingClassMetadata.getAnnotationAttributes(EnableRpcProxies.class.getName());
        if (container != null && container.containsKey("value")) {
            for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
                addAttributesIfNotNull(result, containedAttributes);
            }
        }

        if (result.isEmpty()) {
            return;
        }

        for (AnnotationAttributes attributes : result) {

            // 不开启代理
            boolean enable = attributes.getBoolean("enable");
            if (!enable) {
                continue;
            }

            String host = attributes.getString("host");
            if (!nacosEnable && StringUtils.isEmpty(host)) {
                throw new IllegalArgumentException("spring.cloud.nacos.discovery.enabled为false，则@EnableRpcProxy#host需必填");
            }

            String[] proxyPackages = attributes.getStringArray("proxyPackages");
            String serviceName = attributes.getString("serviceName");
            boolean dynamic = (nacosEnable && dynamicAddr && attributes.getBoolean("dynamicAddr"));

            ClassPathRpcProxyScanner scanner = new ClassPathRpcProxyScanner(registry);

            scanner.setHost(host);
            scanner.setServiceName(serviceName);

            // 扫描路径下的接口
            int scanCount = scanner.scan(proxyPackages);
            if (scanCount < 1) {
                continue;
            }

            // 有代理则注册feign
            registerFeign(registry, serviceName, dynamic ? "" : host);
        }

        // 启动覆盖流程
        if (coverProxy) {
            registerCoverProxy(registry);
        }

    }

    /**
     * 注册代理覆盖流程
     * @param registry 注册工厂
     */
    private void registerCoverProxy(BeanDefinitionRegistry registry) {

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(CoverProxyBeanFactoryPostProcess.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        registry.registerBeanDefinition(CoverProxyBeanFactoryPostProcess.class.getName(), beanDefinition);

    }

    /**
     * 进行feign注册
     * @param registry registry
     * @param serviceName 服务名称
     * @param host 不为空则使用直连模式
     */
    private void registerFeign(BeanDefinitionRegistry registry, String serviceName, String host) {

        // 元数据
        AnnotatedGenericBeanDefinition metaFeignBd = new AnnotatedGenericBeanDefinition(RemoteProcedureCallFeign.class);
        // 元注解
        AnnotationMetadata metadata = metaFeignBd.getMetadata();
        Map<String, Object> attributes = metadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());

        // 构建
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

        builder.addPropertyValue("name", serviceName);
        builder.addPropertyValue("contextId", serviceName);

         builder.addPropertyValue("url", host);
        // 不需要path
         builder.addPropertyValue("path", "");

        builder.addPropertyValue("type", RemoteProcedureCallFeign.class);
        builder.addPropertyValue("decode404", Boolean
                .parseBoolean(String.valueOf(attributes.get("decode404"))));

        Object fallback = attributes.get("fallback");
        if (fallback != null) {
            builder.addPropertyValue("fallback", fallback instanceof Class
                    ? (Class<?>) fallback
                    : ClassUtils.resolveClassName(fallback.toString(), null));
        }
        Object fallbackFactory = attributes.get("fallbackFactory");
        if (fallbackFactory != null) {
            builder.addPropertyValue("fallbackFactory", fallbackFactory instanceof Class
                    ? (Class<?>) fallbackFactory
                    : ClassUtils.resolveClassName(fallbackFactory.toString(),
                    null));
        }

        builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        builder.setLazyInit(true);

        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();

        beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, FeignClientFactoryBean.class.getName());

        // RemoteProcedureCallFeign子类会有多个，所以这里都是false
        beanDefinition.setPrimary(false);

        String[] qualifiers = getQualifiers(attributes);
        if (ObjectUtils.isEmpty(qualifiers)) {
            qualifiers = new String[] { serviceName + "FeignClient" };
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, serviceName,
                qualifiers);
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

    }

    private String[] getQualifiers(Map<String, Object> client) {
        if (client == null) {
            return null;
        }
        List<String> qualifierList = new ArrayList<>(
                Arrays.asList((String[]) client.get("qualifiers")));
        qualifierList.removeIf(qualifier -> !StringUtils.hasText(qualifier));
        if (qualifierList.isEmpty() && getQualifier(client) != null) {
            qualifierList = Collections.singletonList(getQualifier(client));
        }
        return !qualifierList.isEmpty() ? qualifierList.toArray(new String[0]) : null;
    }

    private String getQualifier(Map<String, Object> client) {
        if (client == null) {
            return null;
        }
        String qualifier = (String) client.get("qualifier");
        if (StringUtils.hasText(qualifier)) {
            return qualifier;
        }
        return null;
    }

    private static void addAttributesIfNotNull(
            Set<AnnotationAttributes> result, @Nullable Map<String, Object> attributes) {

        if (attributes != null) {
            result.add(AnnotationAttributes.fromMap(attributes));
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

}
